#ifndef MATHS_H // Use a unique identifier for every file.
#define MATHS_H

#include "stdafx.h"
#include <vector>
#include <iostream>
#include <algorithm>

#define X_AXIS 0
#define Y_AXIS 1
#define Z_AXIS 2

#define INFINITY 1e8
#define EPSILON 0.000001f
using namespace std;

template<typename T>
class Vec3
{
public:
	T x, y, z;
	Vec3() : x(T(0)), y(T(0)), z(T(0)) { }
	Vec3(T xx) : x(xx), y(xx), z(xx) { }

	Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {}
	
	void normalize()
	{
		T nor2 = length();
		if (nor2 > 0) {
			T invNor = 1 / nor2;
			x = x * invNor;
			y= y * invNor;
			z = z * invNor;
		}
	}

	Vec3<T> operator* (const T &f) const { return Vec3<T>(x * f, y * f, z * f); }
	Vec3<T> operator* (const Vec3<T> &v) const { return Vec3<T>(x * v.x, y * v.y, z * v.z); }
	T dot(const Vec3<T> &v) const { return x * v.x + y * v.y + z * v.z; }
	Vec3<T> operator - (const T &f) const { return Vec3<T>(x - f, y - f, z - f); }
	Vec3<T> operator - (const Vec3<T> &v) const { return Vec3<T>(x - v.x, y - v.y, z - v.z); }
	Vec3<T> operator + (const Vec3<T> &v) const { return Vec3<T>(x + v.x, y + v.y, z + v.z); }
	Vec3<T>& operator += (const Vec3<T> &v) { x += v.x, y += v.y, z += v.z; return *this; }
	//Vec3<T>& operator *= (const Vec3<T> &v) { x *= v.x, y *= v.y, z *= v.z; return *this; }
	bool operator == (const Vec3<T> &v) { return x == v.x && y == v.y && z == v.z; }
	Vec3<T> operator - () const { return Vec3<T>(-x, -y, -z); }
	T length2() const { return x * x + y * y + z * z; }
	T length() const { return sqrtf(length2()); }
	/*Vec3<T> CrossProduct(Vec3<T> other)
	{ 
		return Vec3<T>(y*other.z - other.y*z,  z*other.x - x*other.z, x*other.y - y*other.x); 
	}*/
	friend std::ostream & operator << (std::ostream &os, const Vec3<T> &v)
	{
		os << "[" << v.x << " " << v.y << " " << v.z << "]";
		return os;
	}
};

template<typename T>
class Sphere
{
public:
	Vec3<T> center;                         /// position of the sphere
	T radius, radius2;                      /// sphere radius and radius^2
	Vec3<T> surfaceColor, emissionColor;    /// surface color and emission (light)
	T transparency, reflection;             /// surface transparency and reflectivity
	Sphere(const Vec3<T> &c, const T &r, const Vec3<T> &sc, 
		const T &refl = 0, const T &transp = 0, const Vec3<T> &ec = 0) : 
		center(c), radius(r), radius2(r * r), surfaceColor(sc), emissionColor(ec),
		transparency(transp), reflection(refl)
	{}
	// compute a ray-sphere intersection using the geometric solution
	bool intersect(const Vec3<T> &rayorig, const Vec3<T> &raydir, T *t0 = NULL, T *t1 = NULL) const
	{
		Vec3<T> l = center - rayorig;
		T tca = l.dot(raydir);
		if (tca < 0) return false;
		T d2 = l.dot(l) - tca * tca;
		if (d2 > radius2) return false;
		T thc = sqrt(radius2 - d2);
		if (t0 != NULL && t1 != NULL) {
			*t0 = tca - thc;
			*t1 = tca + thc;
		}

		return true;
	}
};

template <typename T>
class Plane
{
	//will be composed of three vectors
public:
	Vec3<T> vertexA, vertexB, vertexC, verrtexD;
	T tileLength, tileWidth;
	Vec3<T> color1, color2;

	Plane(Vec3<T> a, Vec3<T> b , Vec3<T> c, T lengthTile, T widthLength, Vec3<T> color1, Vec3<T> color2 ): 
		vertexA(a), vertexB(b), vertexC(c), tileLength(lengthTile), tileWidth(widthLength), color1(color1) , color2(color2)
	{
		
	}


	bool CheckForIntersection(Vec3<T> &o ,Vec3<T> &d)
	{
		Vec3<T> u , v, n;
		u = vertexB - vertexA;
		v = vertexC - vertexA;
		n = u.CrossProduct(v);
		T dist = n.dot(vertexA);

		T upper = dist - (n.dot(o));
		T lower = d.dot(n);

		T t = upper/lower;

		return t > 0;
	}

	Vec3<T> GetIntersection(Vec3<T> &o, Vec3<T> &d)
	{
		Vec3<T> u , v, n;
		u = vertexB - vertexA;
		v = vertexC - vertexA;
		n = u.CrossProduct(v);
		T dist = n.dot(vertexA);

		T t = (dist - n.dot(o))/d.dot(n);
		
		Vec3<T> output = o + d*t;
		return output.normalize();
	}
};




//each bounding box will contain 4 veticies therefore four Vec3 varibales
//intersection of a ray with bounding box. If so check with triangle

template <typename T>
class BoundingBox
{
private:
	Vec3<T> minPoint, maxPoint;

public:
	BoundingBox()
	{
		minPoint = Vec3<T>(INT_MIN);
		maxPoint = Vec3<T>(INT_MAX);
	}

	BoundingBox(Vec3<T> point)
	{
		minPoint = Vec3<T>(point.x, point.y, point.z);
		maxPoint = Vec3<T>(point.x, point.y, point.z);
	}

	BoundingBox(Vec3<T> minPoint, Vec3<T> maxPoint)
	{
		this->minPoint = Vec3<T>(minPoint.x, minPoint.y, minPoint.z);
		this->maxPoint =  Vec3<T>(maxPoint.x, maxPoint.y, maxPoint.z);
	}

	Vec3<T> GetMaxPoint()
	{
		return this->maxPoint;
	}

	Vec3<T> GetMinPoint()
	{
		return this->minPoint;
	}

	bool CheckIfPointContained(Vec3<T> currentPoint)
	{
		return (currentPoint.x >= this->minPoint.x && currentPoint.x <= this->maxPoint.x && currentPoint.y <= maxPoint.y && currentPoint.y >= minPoint.y && currentPoint.z >= minPoint.z && currentPoint.z <= maxPoint.z);
	}
	
	void Expand(Vec3<T> currentPoint)
	{
		this->minPoint.x = min(minPoint.x, currentPoint.x);
		this->minPoint.y = min(minPoint.y, currentPoint.y);
		this->minPoint.z = min(minPoint.z, currentPoint.z);

		this->maxPoint.x = max(maxPoint.x, currentPoint.x);
		this->maxPoint.y = max(maxPoint.y, currentPoint.y);
		this->maxPoint.z = max(maxPoint.z, currentPoint.z);
	}

	bool Overlaps(BoundingBox<T> *b)
	{
		bool x = b->minPoint.x => this->minPoint.x && b->minPoint.x <= this->minPoint.x;
		bool y = b->minPoint.y => this->minPoint.y && b->minPoint.y <= this->minPoint.y;
		bool z = b->minPoint.z => this->minPoint.z && b->minPoint.z <= this->minPoint.z;

		return x && y && z;
	}

	bool CheckPointInsideBox(Vec3<T> pont)
	{
		bool x = pont.x => this->minPoint.x && pont.x <= this->minPoint.x;
		bool y = pont.y => this->minPoint.y && pont.y <= this->minPoint.y;
		bool z = pont.z => this->minPoint.z && pont.z <= this->minPoint.z;

		return x && y && z;
	}


	void Union(BoundingBox<T> *b)
	{
		this->minPoint.x = min(minPoint.x, b->minPoint.x);
		this->minPoint.y = min(minPoint.y, b->minPoint.y);
		this->minPoint.z = min(minPoint.z, b->minPoint.z);

		this->maxPoint.x = max(maxPoint.x, b->maxPoint.x);
		this->maxPoint.y = max(maxPoint.y, b->maxPoint.y);
		this->maxPoint.z = max(maxPoint.z, b->maxPoint.z);
	}
	

	int GetLongestAxis()
	{
		Vec3<T> diagVector = this->maxPoint - this->minPoint;

		if(diagVector.x > diagVector.y && diagVector.x > diagVector.z)
		{
			return 0;
		}
		else if(diagVector.y > diagVector.x && diagVector.y > diagVector.z)
		{
			return 1;
		}
		else
		{
			return 2;
		}
	}

	bool CheckIntersection(Vec3<T> &o, Vec3<T> &d)
	{
		//Vec3<T> hitVarible ;
		return CheckLineBox(this->minPoint, this->maxPoint, o,d);
	}

	bool CheckLineBox(Vec3<T> bboMin, Vec3<T> bboxMax, Vec3<T> origVec, Vec3<T> dirVec)
	{
		//Vec3<T> invDir(1.0f/ dirVec.x, 1.0f/ dirVec.y, 1.0f/ dirVec.z);
		//T t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, t = 0;
		//t1 = (bboMin.x - origVec.x) * invDir.x;
		//t2 = (bboxMax.x - origVec.x)*invDir.x;
		//t3 = (bboMin.y - origVec.y)*invDir.y;
		//t4 = (bboxMax.y - origVec.y)*invDir.y;
		//t5 = (bboMin.z - origVec.z) * invDir.z;
		//t6 = (bboxMax.z - origVec.z) * invDir.z;

		///*T tmin = max(min(t1, t2), min(t3, t4));
		//T tmax = min(max(t1, t2), max(t3, t4));*/

		//T tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6));
		//T tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6));

		//if(tmax < 0)
		//{
		//	t = tmax;
		//	return false;
		//}

		//if(tmin > tmax)
		//{
		//	t = tmax;
		//	return false;
		//}

		//t = tmin;
		//return true;

		dirVec.normalize();
		T maxS = -FLT_MAX;
		T minT = FLT_MAX;
		T s,t;

		float recipX = 1.0f/dirVec.x;

		if(recipX >= 0.0f)
		{
			s = (this->minPoint.x - origVec.x) * recipX;
			t = (this->maxPoint.x -origVec.x) * recipX;
		}
		else 
		{
			s = (this->maxPoint.x - origVec.x) * recipX;
			t = (this->minPoint.x - origVec.x) * recipX;
		}

		if(s > maxS)
			maxS = s;
		if(t < minT)
			minT = t;

		if(maxS > minT)
			return false;

		float recipY = 1.0f/dirVec.y;

		if(recipY >= 0.0f)
		{
			s = (this->minPoint.y - origVec.y) * recipY;
			t = (this->maxPoint.y -origVec.y) * recipY;
		}
		else 
		{
			s = (this->maxPoint.y - origVec.y) * recipY;
			t = (this->minPoint.y - origVec.y) * recipY;
		}

		if(s > maxS)
			maxS = s;
		if(t < minT)
			minT = t;

		if(maxS > minT)
			return false;

		float recipZ = 1.0f/dirVec.z;

		if(recipZ >= 0.0f)
		{
			s = (this->minPoint.z - origVec.z) * recipZ;
			t = (this->maxPoint.z -origVec.z) * recipZ;
		}
		else 
		{
			s = (this->maxPoint.z - origVec.z) * recipZ;
			t = (this->minPoint.z - origVec.z) * recipZ;
		}

		if(s > maxS)
			maxS = s;
		if(t < minT)
			minT = t;

		if(maxS > minT)
			return false;

		return true;
	}

	Vec3<T> GetLineBoxIntersection(Vec3<T> origVec, Vec3<T> dirVec)
	{
		dirVec.normalize();
		T maxS = -FLT_MAX;
		T minT = FLT_MAX;
		T s,t;

		float recipX = 1.0f/dirVec.x;

		if(recipX >= 0.0f)
		{
			s = (this->minPoint.x - origVec.x) * recipX;
			t = (this->maxPoint.x -origVec.x) * recipX;
		}
		else 
		{
			s = (this->maxPoint.x - origVec.x) * recipX;
			t = (this->minPoint.x - origVec.x) * recipX;
		}

		if(s > maxS)
			maxS = s;
		if(t < minT)
			minT = t;

		if(maxS > minT)
			return false;

		float recipY = 1.0f/dirVec.y;

		if(recipY >= 0.0f)
		{
			s = (this->minPoint.y - origVec.y) * recipY;
			t = (this->maxPoint.y -origVec.y) * recipY;
		}
		else 
		{
			s = (this->maxPoint.y - origVec.y) * recipY;
			t = (this->minPoint.y - origVec.y) * recipY;
		}

		if(s > maxS)
			maxS = s;
		if(t < minT)
			minT = t;

		if(maxS > minT)
			return false;

		float recipZ = 1.0f/dirVec.z;

		if(recipZ >= 0.0f)
		{
			s = (this->minPoint.z - origVec.z) * recipZ;
			t = (this->maxPoint.z -origVec.z) * recipZ;
		}
		else 
		{
			s = (this->maxPoint.z - origVec.z) * recipZ;
			t = (this->minPoint.z - origVec.z) * recipZ;
		}

		if(s > maxS)
			maxS = s;
		if(t < minT)
			minT = t;

		if(maxS > minT)
			return false;

		return origVec + dirVec * minT;
	}



	/*int inline GetIntersection( float fDst1, float fDst2, Vec3<T> P1, Vec3<T> P2, Vec3<T> &Hit) 
	{
		if ( (fDst1 * fDst2) >= 0.0f) return 0;
		if ( fDst1 == fDst2) return 0; 
		Hit = P1 + (P2-P1) * ( -fDst1/(fDst2-fDst1) );
		return 1;
	}

	int inline InBox( Vec3<T> Hit, Vec3<T> B1, Vec3<T> B2, const int Axis)
	{
		if ( Axis==1 && Hit.z > B1.z && Hit.z < B2.z && Hit.y > B1.y && Hit.y < B2.y) return 1;
		if ( Axis==2 && Hit.z > B1.z && Hit.z < B2.z && Hit.x > B1.x && Hit.x < B2.x) return 1;
		if ( Axis==3 && Hit.x > B1.x && Hit.x < B2.x && Hit.y > B1.y && Hit.y < B2.y) return 1;
		return 0;
	}*/

	// returns true if line (L1, L2) intersects with the box (B1, B2)
	// returns intersection point in Hit
	//int CheckLineBox( Vec3<T> B1, Vec3<T> B2, Vec3<T> L1, Vec3<T> L2, Vec3<T> &Hit)
	//{
	//	/*if (L2.x < B1.x && L1.x < B1.x) return false;
	//	if (L2.x > B2.x && L1.x > B2.x) return false;
	//	if (L2.y < B1.y && L1.y < B1.y) return false;
	//	if (L2.y > B2.y && L1.y > B2.y) return false;
	//	if (L2.z < B1.z && L1.z < B1.z) return false;
	//	if (L2.z > B2.z && L1.z > B2.z) return false;
	//	if (L1.x > B1.x && L1.x < B2.x &&
	//		L1.y > B1.y && L1.y < B2.y &&
	//		L1.z > B1.z && L1.z < B2.z) 
	//		{
	//			Hit = L1; 
	//			return true;
	//		}*/
	//	if ( (GetIntersection( L1.x-B1.x, L2.x-B1.x, L1, L2, Hit) && InBox( Hit, B1, B2, 1 ))
	//	  || (GetIntersection( L1.y-B1.y, L2.y-B1.y, L1, L2, Hit) && InBox( Hit, B1, B2, 2 )) 
	//	  || (GetIntersection( L1.z-B1.z, L2.z-B1.z, L1, L2, Hit) && InBox( Hit, B1, B2, 3 )) 
	//	  || (GetIntersection( L1.x-B2.x, L2.x-B2.x, L1, L2, Hit) && InBox( Hit, B1, B2, 1 )) 
	//	  || (GetIntersection( L1.y-B2.y, L2.y-B2.y, L1, L2, Hit) && InBox( Hit, B1, B2, 2 )) 
	//	  || (GetIntersection( L1.z-B2.z, L2.z-B2.z, L1, L2, Hit) && InBox( Hit, B1, B2, 3 )))
	//		return true;

	//	return false;
	//}

};

template <typename T>
class Ray
{
public:
	Vec3<T> dir;
	Vec3<T> orig;

	Ray(Vec3<T> o, Vec3<T> d)
	{
		this->dir = d;
		this->orig = o;
	}
};


template <typename T>
class Triangle
{
private:
	//will be composed of three vertices 
	
	//BoundingBox<T> trinagleBoundingBox;

public:

	Vec3<T> vertA, vertB, vertC;
	Triangle()
	{

	}

	Triangle(Vec3<T> &a, Vec3<T> &b , Vec3<T> &c) : vertA(a), vertB(b), vertC(c){}


	void Translate(Vec3<T> amount)
	{
		vertA += amount;
		vertB += amount;
		vertC += amount;
	}


	
	//Checks if a specific 
	bool CheckRayIntersection(Vec3<T> &o, Vec3<T> &d)
	{
#pragma region oldIntersection
		/*Vec3<T> e1 = vertB - vertA;
		Vec3<T> e2 = vertC - vertA;
		Vec3<T> s1 = d - vertA;

		T divisor = s1.dot(e1);

		Vec3<T> div = o - vertB;

		float invDiv = 1.f/ divisor;

		T b1 = d.dot(s1) * invDiv;

		if(b1 < 0. || b1 > 1.)
			return false;

		Vec3<T> s2 = div.CrossProduct(e1);
		T b2 = d.dot(s2) * invDiv;

		if(b2 < 0. || b1 + b2 > 1)
			return false;

		return true;*/

#pragma endregion
		Vec3<T> e1 = vertB - vertA;
		Vec3<T> e2 = vertC - vertA;
		//Vec3<T> n = e1.CrossProduct(e2);
		Vec3<T> n (0);
		//Vec3<T> p = d.CrossProduct(e2);
		Vec3<T> p(0);
		d.normalize();
		

		T a = e1.dot(p);

		if(a < EPSILON && a > -EPSILON)
			return false;

		float f = 1.0f/a;

		Vec3<T> s = o - vertA;
		T aa = n.dot(s);
		T bb = n.dot(d);
		T tt = -aa/bb;
		Vec3<T> output = o + d * tt;
		T u = f * s.dot(p);

		 if(u < 0.0f || u > 1.0f)
			return false;
		 //Vec3<T> q = s.CrossProduct(e1);
		 Vec3<T> q(0);
		 float v = f * q.dot(d);
		 if(v < 0.0f || u+v > 1.0f)
			return false;

		return true;
		
	}

	Vec3<T> GetIntersectionPoint(Vec3<T> &o, Vec3<T> &d, T &t0, T &t1)
	{
		Vec3<T> e1 = vertB - vertA;
		Vec3<T> e2 = vertC - vertA;
		Vec3<T> p = e1.CrossProduct(e2);
		T a = e1.dot(p);

		if(a == 0)
			return false;

		float f = 1.0f/a;

		Vec3<T> s = o - vertA;
		T u = f * s.dot(p);
		if(u < 0.0f || u > 1.0f)
			return false;

		Vec3<T> q = s.CrossProduct(e1);
		T v = f * d.dot(q);

		if(v < 0.0f || u+v > 1.0f)
			return false;

		T t = f * e2.dot(q);

		t1 = INT_MAX;
		t0 = t;
		Vec3<T> output = o + d*t;
		return output;
	}

	T GetDistanceIntersection(Ray<T> *ray)
	{
		return GetDistanceIntersection(ray->orig, ray->dir);
	}


	T GetDistanceIntersection(Vec3<T> &o, Vec3<T> &d)
	{
		Vec3<T> e1 = vertB - vertA;
		Vec3<T> e2 = vertC - vertA;
		Vec3<T> n = e1.CrossProduct(e2);
		Vec3<T> p = d.CrossProduct(e2);
		d.normalize();
		

		T a = e1.dot(p);

		if(a < EPSILON && a > -EPSILON)
			return false;

		float f = 1.0f/a;

		Vec3<T> s = o - vertA;
		T aa = n.dot(s);
		T bb = n.dot(d);
		T tt = -aa/bb;
		Vec3<T> output = o + d * tt;
		
		return tt;
	}

	Vec3<T> GetTriangleNormal()
	{
		Vec3<T> u , v, n;
		u = vertB - vertA;
		v = vertC - vertA;
		n = u.CrossProduct(v);
		return n;
	}

	Vec3<T> GetTrinagleMidpoint()
	{
		return Vec3<T>((vertA.x + vertB.x + vertC.x)/3, (vertA.y + vertB.y + vertC.y)/ 3, (vertA.z+ vertB.z+ vertC.z)/ 3);
	}
};


#endif